﻿using System;
using System.Threading;
using Com.PhilChuang.Pmvp.Model;
using Com.PhilChuang.Pmvp.Ui.Views;
using Com.PhilChuang.Utils;

namespace Com.PhilChuang.Pmvp.Ui.Presenters
{
	public abstract class AbstractAppPresenter<TView, TApp> : AbstractGenericPresenter<TView>
		where TView : class, IView
		where TApp : class, IApp
	{
		#region Instance Variables
		private readonly ManualResetEvent m_AppInitializedMre = new ManualResetEvent (false);
		private readonly ManualResetEvent m_WaitForAppSuspendingMre = new ManualResetEvent (false);
		private readonly ManualResetEvent m_AppSuspendingWorkFinishedMre = new ManualResetEvent (false);
		#endregion

		#region Properties
		public TApp App 
		{ get; private set; }

		private void SetApp (TApp app)
		{
			app.ThrowIfNull ("app");

			if (App != null)
				throw new ArgumentException ("Already registered an app");

			App = app;
			SubscribeToAppEvents ();
			OnAppRegistered ();
		}
		#endregion

		#region Constructors / Init / Dispose
		protected AbstractAppPresenter (TApp app, TView view) : base (view)
		{
			SetApp (app);
		}

		/// <summary>
		/// Disposes of held resources.
		/// Call base impl as the last step.
		/// </summary>
		protected override void OnDispose ()
		{
			// A View Closing (thus calling Dispose) SHOULD BE EXCLUSIVE to AppSuspending ... 
			// but on the chance it's not, wait for DoAppSuspendingWork to finish.
			if (WaitForAppSuspending ())
				WaitForAppSuspendingWorkFinished ();
			UnsubscribeToAppEvents ();
			base.OnDispose ();
		}
		#endregion

		#region To implement
		/// <summary>
		/// Whether or not the App should load data asynchronously. Default impl returns false.
		/// </summary>
		protected virtual bool LoadDataAsync
		{ get { return false; } }

		/// <summary>
		/// The amount of time to wait during the Dispose method in order to catch the AppSuspending event
		/// </summary>
		protected virtual int WaitForAppSuspendingMs
		{ get { return 250; } }

		/// <summary>
		/// Subscribe to App events after the App is registered during Presenter instantiation.
		/// Call base impl as the first step.
		/// </summary>
		protected virtual void SubscribeToAppEvents ()
		{
			App.Initialized += OnAppInitialized;
			App.Suspending += OnAppSuspending;
		}

		/// <summary>
		/// Fires when the App is registered with the Presenter during Presenter instantiation.
		/// Call base impl as first step.
		/// </summary>
		protected virtual void OnAppRegistered ()
		{
			CheckAppInitAsync ();
		}

		/// <summary>
		/// Unsubscribes to View events after the View has Closed.
		/// Call base impl as the first step.
		/// </summary>
		protected virtual void UnsubscribeToAppEvents ()
		{
			App.Initialized -= OnAppInitialized;
			App.Suspending -= OnAppSuspending;
		}
		#endregion

		#region App Events
		/// <summary>
		/// Runs when the application has finished initializing.
		/// Call base impl as the last step.
		/// </summary>
		protected virtual void OnAppInitialized ()
		{
			DebugWriteLine ("OnAppInitialized");
			m_AppInitializedMre.Set ();
		}

		/// <summary>
		/// Threadblocks until the App has initialized
		/// </summary>
		protected void WaitForAppInit ()
		{
			if (App.IsInitialized) return;
			m_AppInitializedMre.WaitOne ();
		}

		/// <summary>
		/// Waits to see if the App suspends
		/// </summary>
		/// <returns>true if the App is suspending</returns>
		private bool WaitForAppSuspending ()
		{
			return m_WaitForAppSuspendingMre.WaitOne (WaitForAppSuspendingMs);
		}

		/// <summary>
		/// Waits for the App suspending to finish
		/// </summary>
		private void WaitForAppSuspendingWorkFinished ()
		{
			m_AppSuspendingWorkFinishedMre.WaitOne ();
		}

		/// <summary>
		/// Method that runs when the application is suspending, use it for persisting view state.
		/// Call base impl as the first step.
		/// </summary>
		protected void OnAppSuspending ()
		{
			DebugWriteLine ("OnAppSuspending");
			m_WaitForAppSuspendingMre.Set ();
			DoAppSuspendingWork ();
			m_AppSuspendingWorkFinishedMre.Set ();
		}

		/// <summary>
		/// Overridable method to do work when the App is suspending.
		/// Call base impl as the first step.
		/// </summary>
		protected virtual void DoAppSuspendingWork ()
		{
			// does nothing
		}
		#endregion

		#region Helper methods
		protected virtual void CheckAppInitAsync ()
		{
			ExecuteAsync (() =>
			              	{
			              		if (!App.IsInitialized) App.Init (LoadDataAsync);
			              	});
		}
		#endregion
	}
}